﻿/** Pete's Tools package */
package pete.animation {
	
	import flash.display.*;
	import flash.utils.*;
	import flash.events.*;
	
	/**
	 * @version
	 * 0.1
	 *
	 * @description 
	 * Singleton class to animate Object properties.
	 * 
	 * @example 
	 * In this example we create a Sprite and issue a tween command for <code>x</code> and <code>y</code> properties. 
	 * Both properties would finish tweening after one second. Moments later we issue another command that overwrites the <code>x</code> tween 
	 * and adds a <code>rotation</code> tween:
	 * <code><pre>
	 * var move:Mover = Mover.getInstance();
	 * <br />
	 * var test:Sprite = new Sprite();
	 * test.graphics.beginFill(0x334379);
	 * test.graphics.drawRect(0,0,100,100);
	 * test.graphics.endFill();
	 * this.addChild(test);
	 * <br />
	 * move.tween(test, {"x":200, "y":40}, 1000, 15);
	 * move.tween(test, {"x":300, "rotation":60}, 2000, 15);
	 * </pre></code>
	 * Notice that <code>y</code> tween finished at 1000 miliseconds while <code>x</code> target value was overwriten and the tween finished at 2000 miliseconds.
	 */
	public class Mover extends EventDispatcher {

		private static var xx:Object = new Dictionary();
		private static var instance:Mover = null;
		private static var allowInstantiation:Boolean = false;
		
		/**
		 * Returns the single global instance of this class.
		 */
		public static function getInstance():Mover
		{
			if ( Mover.instance == null )
			{
				Mover.allowInstantiation = true;
				Mover.instance = new Mover();
				Mover.allowInstantiation = false;
			}
			
			return Mover.instance;
		}
		
		
		/**
		 * Singleton constructor. Use <code>Mover.getInstance();</code> instead.
		 */
		public function Mover()
		{
			if (getQualifiedClassName(super) == "pete.tools::Mover" )
			{
				if (!allowInstantiation)
				{
					throw new Error("Error: Instantiation failed: Use Mover.getInstance() instead of new Mover().");
				}
			}
		}
		
		
		
		/**
		 * @param target Reference to an object that will be manipulated.
		 * @param properties Contains target values for manipulation. Ex: <code>{"x":10, "y":30, "alpha":0.4}</code>
		 * @param duration Time for tweening value A to value B in seconds.
		 * @param speed Delay between tween calculations. Lower number = smoother transition but more CPU intensive. @default 30
		 * @param type Reference ID to one of 30 easing equations that calculate tween curvature (By Robert Penner). @default 26
		 * @param arguments An Object filled with extra arguments that can be passed to the MoverEvent Object.
		 *
		 * 
		 */
		public function tween (target:*, properties:Object, duration:Number, speed:int = 15, type:int = 19, arguments:Object = null):void
		{
			if (xx[target] == undefined)
			{
				xx[target] 			= new Object ();
				xx[target].propFrom		= new Object ();
				xx[target].propChange 	= new Object ();
				xx[target].propTo 		= new Object ();
				xx[target].timer 		= new Object ();
				xx[target].speed 		= new Object ();
				xx[target].duration 	= new Object ();
				xx[target].now 		= new Object ();
				xx[target].type 		= new Object ();
				xx[target].arguments	= new Object ();
			}
			
			for (var prop:String in properties)
			{
				xx[target].propFrom[prop] 	= target[prop];
				xx[target].propChange[prop] 	= properties[prop] - target[prop];
				xx[target].propTo[prop] 		= properties[prop];
				
				if(xx[target].timer[prop] != undefined)
				{
					clearInterval (xx[target].timer[prop]);	
				}
				
				xx[target].timer[prop] 		= setInterval (calculate, speed, target, prop);
				xx[target].duration[prop] 	= duration*1000;
				xx[target].speed[prop] 		= speed;
				xx[target].now[prop] 		= 0;
				xx[target].type[prop] 		= type;

				if (arguments != null)
				{
					xx[target].arguments[prop]	= arguments;
				}
			}
		}
		
		
		private function calculate (target:*, prop:String):void
		{
			if(xx[target].now[prop] < xx[target].duration[prop])
			{
				xx[target].now[prop] += xx[target].speed[prop];
				target[prop] = Easing.ease (xx[target].type[prop], xx[target].now[prop], xx[target].propFrom[prop], xx[target].propChange[prop], xx[target].duration[prop]);
			}
			else 
			{
				target[prop] = xx[target].propTo[prop];
				
				if (xx[target].arguments[prop] != null)
				{
					target.dispatchEvent (new MoverEvent (MoverEvent.MOTION_COMPLETE, xx[target].arguments[prop]));
				}
				else
				{
					target.dispatchEvent (new MoverEvent (MoverEvent.MOTION_COMPLETE));
				}
				
				clearInterval(xx[target].timer[prop]);
			}
		}
	}	
}